/****************************************************************************
*
* Description: Memory region abstraction
* Created at: 14.11.06 02:48:22 UTC
*
* $Revision$
*
****************************************************************************/
#pragma once

#if !defined(__common_memory_block_h__)
#define __common_memory_block_h__

#include <vector>						/* linear address space.  */
#include <iomanip>						/* debug output require manipulators. */
#include <ostream>						/* required for ostream.  */
#include <common/types.h>				/* common types.  */
#include <common/resource_descriptor.h>	/* memory_block can have assigned properties.  */
#include <common/exception.h>			/* and raise exceptions.  */

namespace common {
namespace memory {

/* Defines address type in the system. So, by default
 * we have 32-bit linear address space.  */
/* TODO: what we can do to make it portable?  */
typedef uint32		addr_t;

namespace consts
{
const   addr_t      bad_addr    = (addr_t)(-1);
const   addr_t      null_addr   = (addr_t)(0);
}; /* End of consts  */

/* Some module-specific exceptions.  */
DEFINE_NEW_ERROR(invalid_base);
DEFINE_NEW_ERROR(invalid_block);
DEFINE_NEW_ERROR(invalid_address);

/* Linear memory region in the form (region_start_address, region_size).  */
template <
    typename mem_traits,
    class    mem_storage = std::vector<mem_traits>
>
class basic_memory_block :
	public resource_descriptor
{
public:
    typedef mem_traits  item_t;
    typedef mem_storage container_t;

    static const    item_t	mem_init;
    static const    uint32	id;

    /* Default ctor.  */
    basic_memory_block ()
    	: resource_descriptor(id)
    	, start_(consts::bad_addr)
    {}

    /* Initialized ctor. Defines memory region (start, size).  */
    basic_memory_block (addr_t start, size_t size)
    	: resource_descriptor(id)
    	, start_(start)
    	, memory_block_(size, mem_init)
    {}

    /* Copy ctor.  */
    basic_memory_block (const basic_memory_block& o)
    	: resource_descriptor(static_cast<const resource_descriptor&>(o))
    	, start_(o.start_)
    {
        memory_block_.assign(o.memory_block_.begin(), o.memory_block_.end());
    }

    /* Return memory cell data using absolute linear address.  */
    item_t& get_by_abs (addr_t addr)
    {
        if( start_ == consts::bad_addr )
            THROW(invalid_base, "Base not defined\n");

        if( addr == consts::bad_addr )
            THROW(invalid_address, "Out of range\n");

        return get_by_offs(addr - start_);
    }

	/* Return memory cell data using relative linear address.  */
    item_t& get_by_offs (addr_t offs)
    {
        if( !memory_block_.size() )
            THROW(invalid_block, "Zero-size block\n");

        if( offs > memory_block_.size() )
            THROW(invalid_address, "Out of range\n");

        return memory_block_[offs];
    }

	/* Return memory cell data using relative linear address.  */
    item_t& operator[] (addr_t offs)
    {
        return get_by_offs(offs);
    }

    /* Control memory block size.  */
    void resize (size_t size)
    {
        container_t				temp_(size, mem_init);

        memory_block_.swap(temp_);

        if( size < temp_.size() )
        {
            std::copy(temp_.begin(), temp_.begin() + size, memory_block_.begin());
        }
        else
        {
            std::copy(temp_.begin(), temp_.end(), memory_block_.begin());
        }
    }

	/* Return memroy block size.  */
    size_t size () const
    {
        return memory_block_.size();
    }

    /* Control memory block base.  */
    void rebase (addr_t addr)
    {
        start_ = addr;
    }

	/* Return memory block base.  */
    addr_t base () const
    {
        return start_;
    }

    /* Memory block direct access.  */
    container_t& block ()
    {
        return memory_block_;
    }

    /* Compare two memory blocks for equivalence (data not compare).  */
    bool operator == (const basic_memory_block& r)
    {
        return start_ == r.start_ && memory_block_ == r.memory_block_;
    }

    /* Debug stuff. Second argument never used and placed for correct template resolution.  */
    template<class T>
    void dump(std::ostream& o, T&)
    {}

    template<>
    void dump(std::ostream& o, uint8&)
    {
        size_t  i, j, s;
        char    ofill;

        ofill = o.fill('0');
        o << std::hex << std::right;
        for( i = 0, s = size(); i < s; )
        {
            o << std::endl << std::setw(8) << i + base() << ": ";

            for( j = 0; j < 0x10; ++j )
            if( i + j < s ) o << std::setw(2) << (uint16) memory_block_[i + j] << " ";
            else            o << "   ";

            for( j = 0; j < 0x10 && i < s; ++j, ++i )
                o << std::setw(1) << (char)(memory_block_[i] < ' ' ? '.' : memory_block_[i]);
        }
        o << std::setw(1) << std::left << std::dec << std::endl;
        o.fill(ofill);
    }

    virtual void dump(std::ostream& o)
    {
        this->resource_descriptor::dump(o);

		/* Checks that block correct and data available.  */
        get_by_abs(start_);
        get_by_offs(0);

		/* Ok, let's use correct version of dump.  */
        item_t unused;
        dump(o, unused);
    }

protected: 
    addr_t		start_;
    container_t	memory_block_;
};

/* Static members initialization.  */
template <
    typename mem_traits,
    class    mem_storage
>
const typename basic_memory_block<mem_traits, mem_storage>::item_t \
basic_memory_block<mem_traits, mem_storage>::mem_init = consts::null_addr;

template <
    typename mem_traits,
    class    mem_storage
>
const uint32 basic_memory_block<mem_traits, mem_storage>::id = 'mblk';

/* Declare default linear memory block of bytes.  */
typedef basic_memory_block<uint8>   memory_block_t;

} /* End of memory  */
} /* End of common  */

#endif  /* End of __common_memory_block_h__  */
